﻿#include "Client.h"
#include <iostream>
// #include "apue.h"
using namespace std;
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <cstdio>

#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cassert>
#include <string>
#include <string.h>
#include <regex>
#include <errno.h>

/*
    global variables
*/
enum OPTION 
{
    OPT_l,
    OPT_s,
    OPT_p,
    OPT_d,
    OPT_o,
    OPT_h,
    OPT_NUM
};

// ftp client instance
Client *fclient;

// preserve all of the options
int fclient_options[OPT_NUM];

// ftp server port
int u_port;

// ftp server address
char* u_address;

// output file name
char* u_output_file_name;

// download file name
char* u_download_file_name;

bool *is_observed;

// check if the options are valid
bool is_valid = true;

/*
    declare functions
*/

// get the command of the user and preserve the options
void get_options(char *argv[], int cur, int num_args);

void handle_options();

void client_request();

void set_invalid();

bool get_port(char *argv[], int cur, int num_args);

bool get_address(char *argv[], int cur, int num_args);

bool get_out_file_name(char *argv[], int cur, int num_args);

bool get_dl_file_name(char *argv[], int cur, int num_args);

void check_port_valid(char *argv[], int cur, int num_args);

void check_address_valid(char *argv[], int cur, int num_args);


int main(int argc, char *argv[])
{
    // allocate memory for the arrays
    memset(fclient_options, 0, OPT_NUM * sizeof(int));
    is_observed = new bool[argc];
    for(int i = 0; i < argc; i++) is_observed[i] = false;
    
    int num_args = argc - 1;
    if(num_args == 0)
    {
        fclient_options[OPT_l] = 1;
    }

    // read the command of user
    for(int i = 1; i < argc; i++)
    {
        if(!is_valid) exit(1);
        if(!is_observed[i] && argv[i][0] == '-')
        {
            get_options(argv, i, num_args);
            is_observed[i] = true;
        }
        else if(!is_observed[i])
        {
            set_invalid();
            is_observed[i] = true;
        }
    }

    if(!is_valid) exit(1);

    // set port, address, download_file_name, output_file_name or print help
    handle_options();

    // start fclient and connect to fserver
    client_request();

    return 0;
    
}

void get_options(char *argv[], int cur, int num_args)
{
    char *cmd = argv[cur];
    // check if the cmd is null
    assert(cmd);
    fclient_options[OPT_l] = 1;
    // get all the options of the cmd
    if(0 == strcmp(cmd, "-h") || 0 == strcmp(cmd, "--help"))
    {
        fclient_options[OPT_h] = 1;
    }
    else if(0 == strcmp(cmd, "-l") || 0 == strcmp(cmd, "--list"))
    {
        fclient_options[OPT_l] = 1;
    }
    else if(0 == strcmp(cmd, "-s") || 0 == strcmp(cmd, "--server"))
    {
        check_address_valid(argv, cur, num_args);
    }
    else if(0 == strcmp(cmd, "-p") || 0 == strcmp(cmd, "--port"))
    {
        check_port_valid(argv, cur, num_args);
    }
    else if(0 == strcmp(cmd, "-d") || 0 == strcmp(cmd, "--dl"))
    {
        fclient_options[OPT_l] = 0;
        if(get_dl_file_name(argv, cur, num_args)) fclient_options[OPT_d] = 1;
        else set_invalid();
    }
    else if(0 == strcmp(cmd, "-o") || 0 == strcmp(cmd, "--output"))
    {
        if(get_out_file_name(argv, cur, num_args)) fclient_options[OPT_o] = 1;
        else set_invalid();
    }
    else
    {
        // cmd is invalid
        set_invalid();
    }
}

void handle_options()
{
    fclient = new Client();
    if(fclient_options[OPT_h])
    {
        Client::print_help();
        exit(0);
    }
    if(fclient_options[OPT_p])
    {
        fclient->set_port(u_port);
    }
    if(fclient_options[OPT_s])
    {
        fclient->set_address(u_address);
    }
    if(fclient_options[OPT_d])
    {
        fclient->set_download_file_name(u_download_file_name);
    }
    if(fclient_options[OPT_o])
    {
        fclient->set_output_file_name(u_output_file_name);
    }
}

void client_request()
{
    // create socket
    if(fclient->create_socket() == -1)
    {
        printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
        exit(1);
    }
    // init address
    fclient->init_address();
    // connect socket
    if(fclient->connect_socket() < 0)
    {
        printf("failed to connect to %s:%d\n", fclient->get_address(), fclient->get_port());
        exit(1);
    }
    else
    {
        printf("succeed to connect to %s:%d\n", fclient->get_address(), fclient->get_port());
    }
    if(fclient_options[OPT_l])
    {
        fclient->request_list();
    }
    else if(fclient_options[OPT_d])
    {
        fclient->request_file(fclient_options[OPT_d], fclient_options[OPT_o]);
    }
    fclient->close_sockfd();
}

void set_invalid()
{
    is_valid = false;
    Client::print_help();
}

bool get_port(char *argv[], int cur, int num_args)
{
    if(cur >= num_args) return false;
    else
    {
        int next = cur + 1;
        char *c_port = argv[next];
        assert(c_port);
        is_observed[next] = true;

        if(c_port[0] == '-')
        {
            set_invalid();
            exit(1);
        }

        // utilize regular expression to check if the port is valid
        regex regexStr("^[\\d]*$");
        smatch matchResult;

        string temp_port(c_port);
        int port;
        if(std::regex_match(temp_port, matchResult, regexStr))
        {
            port = stoi(temp_port);
        }
        else return false;

        if(port < 0 || port > 65535) return false;
        else
        {
            u_port = port;
            return true;
        }
    }
}

void check_port_valid(char *argv[], int cur, int num_args)
{
    if(get_port(argv, cur, num_args))
    {
        fclient_options[OPT_p] = 1;
    }
    else set_invalid();
}

bool get_address(char *argv[], int cur, int num_args)
{
    if(cur >= num_args) return false;
    else
    {
        int next = cur + 1;
        char *c_address = argv[next];
        assert(c_address);
        is_observed[next] = true;

        if(c_address[0] == '-')
        {
            set_invalid();
            exit(1);
        }

        // utilize regular expression to check if the address is valid
        regex regexStr("^[\\d]{1,3}\\.[\\d]{1,3}\\.[\\d]{1,3}\\.[\\d]{1,3}$");
        smatch matchResult;
        string temp_address(c_address);

        if(std::regex_match(temp_address, matchResult, regexStr))
        {
            u_address = c_address;
            return true;
        }
        else
        {
            printf("failed to connect fserver: %s is not a valid address", c_address);
            return false;
        }
    }
}

void check_address_valid(char *argv[], int cur, int num_args)
{
    if(get_address(argv, cur, num_args))
    {
        fclient_options[OPT_s] = 1;
    }
    else set_invalid();
}

bool get_dl_file_name(char *argv[], int cur, int num_args)
{
    if(cur >= num_args) return false;
    else
    {
        int next = cur + 1;
        char *c_dl_file = argv[next];
        assert(c_dl_file);
        is_observed[next] = true;

        u_download_file_name = c_dl_file;
        return true;
    }
}

bool get_out_file_name(char *argv[], int cur, int num_args)
{
    if(cur >= num_args) return false;
    else
    {
        int next = cur + 1;
        char *c_out_file = argv[next];
        assert(c_out_file);
        is_observed[next] = true;

        u_output_file_name = c_out_file;
        return true;
    }
}

